package com.kkb.mybatis.test.mybatis;

import com.kkb.mybatis.framework.config.BoundSql;
import com.kkb.mybatis.framework.config.Configuration;
import com.kkb.mybatis.framework.config.MappedStatement;
import com.kkb.mybatis.framework.config.ParameterMapping;
import com.kkb.mybatis.framework.sqlnode.*;
import com.kkb.mybatis.framework.sqlsource.DynamicSqlSource;
import com.kkb.mybatis.framework.sqlsource.RawSqlSource;
import com.kkb.mybatis.framework.sqlsource.SqlSource;
import com.kkb.mybatis.po.User;
import org.apache.commons.dbcp.BasicDataSource;
import org.dom4j.*;
import org.dom4j.io.SAXReader;
import org.junit.Test;

import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.*;

public class MybatisV2 {
    private Configuration configuration;
    private String namespace;
    private Boolean isDynamic = false;  //是否含有${}或动态标签
    @Test
    public void test() {

        // 加载配置文件
        loadXML("mybatis-config-old.xml");
        // 执行查询语句
        Map<String, Object> param = new HashMap();
        param.put("username", "王五");
        param.put("sex", 1);
        List<User> users = selectList("test.queryUserByParams", param);
        System.out.println(users);
    }

    private <T> List<T> selectList(String statementId, Map<String, Object> param) {

        List<T> results = new ArrayList();
        Connection connection = null;
        ResultSet resultSet = null;
        Statement statement = null;
        try {
            // 连接数据库
            connection = getConnection();
            //LoadXML 已加载
            MappedStatement mappedStatement = configuration.getMappedStatementById(statementId);
            // 获取sql语句
            // String sql = getSql(mappedStatement, param);
            SqlSource sqlSource = mappedStatement.getSqlSource();
            //参数
            BoundSql boundSql = sqlSource.getBoundSql(param);
            String sql = boundSql.getSql();

            statement = createStatement(mappedStatement, sql, connection);

            // 设置参数
            setParameters(param, statement, boundSql);

            //执行查询
            resultSet = executeQuery(statement);

            //处理结果
            handleResult(resultSet, mappedStatement, results);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        return results;
    }

    private <T> void handleResult(ResultSet resultSet, MappedStatement mappedStatement, List results) throws Exception {
        // 获取返回结果类型
        Class resultTypeClazz = mappedStatement.getResultTypeClass();

        // 构造器
        Constructor constructor = resultTypeClazz.getConstructor();

        Object result = null;
        while (resultSet.next()) {
            result = constructor.newInstance();
            ResultSetMetaData metaData = resultSet.getMetaData();
            int columnCount = metaData.getColumnCount();
            for (int i = 0; i < columnCount; i++) {
                String columnName = metaData.getColumnName(i + 1);
                Field field = resultTypeClazz.getDeclaredField(columnName);
                field.setAccessible(true);
                field.set(result, resultSet.getObject(columnName));
            }
            results.add((T) result);
        }
    }

    private ResultSet executeQuery(Statement statement) throws Exception {
        if (statement instanceof PreparedStatement) {
            PreparedStatement preparedStatement = (PreparedStatement) statement;
            return preparedStatement.executeQuery();
        }
        return null;
    }

    private void setParameters(Object param, Statement statement, BoundSql boundSql) throws SQLException {
       //参数赋值
        if (statement instanceof PreparedStatement) {
            PreparedStatement preparedStatement = (PreparedStatement) statement;
            if (param instanceof Integer || param instanceof String) {
                preparedStatement.setObject(1, param);
            } else if (param instanceof Map) {
                //参数
                Map<String, Object> map = (Map<String, Object>) param;
                //TODO parameterMappings 入参封装
                List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
                for (int i = 0; i < parameterMappings.size(); i++) {
                    ParameterMapping parameterMapping = parameterMappings.get(i);
                    String name = parameterMapping.getName();
                    Object value = map.get(name);
                    preparedStatement.setObject(1 + i, value);
                }
            } else {
                // todo
            }
        }
    }

    private Statement createStatement(MappedStatement statement, String sql, Connection connection) {
        try {
            String statementType = statement.getStatementType();
            if (statementType.equals("prepared")) {
                return connection.prepareStatement(sql);
            } else {
                //todo
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    private String getSql(MappedStatement statement, Object param) {
        // todo
        SqlSource sqlSource = statement.getSqlSource();
        BoundSql boundSql = sqlSource.getBoundSql(param);
        String sql = boundSql.getSql();
        return sql;
    }

    private Connection getConnection() {
        try {
            Connection connection = configuration.getDataSource().getConnection();
            return connection;
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    //加载XML
    private void loadXML(String location) {
        //加载配置
        configuration = new Configuration();
        InputStream inputStream = getInputStreamAsResource(location);
        Document document = getDocument(inputStream);
        //加载主配置文件
        parseConfiguration(document.getRootElement());
    }

    private void parseConfiguration(Element rootElement) {
        Element environments = rootElement.element("environments");
        //数据库信息的加载
        parseEnvironment(environments);

        Element mappers = rootElement.element("mappers");

        //mapper解析
        parseMappers(mappers);
    }

    private void parseMappers(Element mappers) {
        List<Element> elementList = mappers.elements("mapper");
        for (Element element : elementList) {
            String resource = element.attributeValue("resource");
            InputStream inputStream = getInputStreamAsResource(resource);
            Document document = getDocument(inputStream);
            parseMapper(document.getRootElement());
        }
    }

    //解析Mapper
    private void parseMapper(Element element) {
        namespace = element.attributeValue("namespace");
        //以select为例
        List<Element> elements = element.elements("select");
        for (Element element1 : elements) {
            parseStatementElement(element1);
        }
    }

    private void parseStatementElement(Element element) {
        String statementId = element.attributeValue("id");
        if (statementId == null || "".equals(statementId)) {
            return;
        }
        // namespace+id  确定一条SQL  paramType===>基本忽略 Mybatis中也是
        statementId = namespace + "." + statementId;
        String resultType = element.attributeValue("resultType");
        Class<?> resultTypeClazz = resolve(resultType);
        String statementType = element.attributeValue("statementType");
        statementType = statementType == null || "".equals(statementType) ? "prepared" : statementType;

        //sqlSource ===>多个sqlNode
        SqlSource sqlSource = createSqlSource(element);

        MappedStatement mappedStatement = new MappedStatement(statementId,statementType,resultType, resultTypeClazz,sqlSource);
        configuration.addMappedStatement(statementId, mappedStatement);

    }

    private SqlSource createSqlSource(Element element) {
        //解析所有sql节点，最终封装到mixedSqlNode中
        //TODO 解析获取SqlSource
        SqlSource sqlSource = parseScriptNode(element);
        return sqlSource;
    }


    //多态解析
    private SqlSource parseScriptNode(Element element) {
        //同级SqlNode   这里解析
        SqlNode mixedSqlNode = parseDynamicTags(element);


        //getBoundSql 接受入参sql解析
        SqlSource sqlSource;
        if (isDynamic) {
            //含有${}或动态标签
            sqlSource = new DynamicSqlSource(mixedSqlNode);
        } else {
            sqlSource = new RawSqlSource(mixedSqlNode);
        }
        return sqlSource;
    }

    //SqlNode 封装
    private SqlNode parseDynamicTags(Element selecteElement) {

        //用于存储所有sqlNode节点
        ArrayList<SqlNode> sqlNodes = new ArrayList<>();

        //获取selectElement所有的子元素  包括所有文本节点和Element
        int count = selecteElement.nodeCount();

        for (int i = 0; i < count; i++) {

            Node node = selecteElement.node(i);

            if (node instanceof Text) {
                String text = node.getText();
                if (node == null) {
                    continue;
                }
                if ("".equals(text.trim())) {
                    continue;
                }

                //封装了带有${}的sql文本
                TextSqlNode textSqlNode = new TextSqlNode(text.trim());
                //判断是否带有${}
                if (textSqlNode.isDynamic()) {
                    sqlNodes.add(textSqlNode);
                    isDynamic = true;
                } else {
                    sqlNodes.add(new StaticTextSqlNode(text.trim()));
                }
            } else if (node instanceof Element) {
                isDynamic = true;
                Element element = (Element) node;
                String name = element.getName();
                //解析动态标签
                if ("if".equals(name)) {
                    String test = element.attributeValue("test");
                    // 递归  搞明白
                    SqlNode sqlNode = parseDynamicTags(element);
                    IfSqlNode ifSqlNode = new IfSqlNode(test, sqlNode);

                    sqlNodes.add(ifSqlNode);
                } else {
                    //todo
                }
            } else {
                //  todo
            }
        }

        return new MixedSqlNode(sqlNodes);
    }

    private Class<?> resolve(String resultType) {
        try {
            Class<?> resultTypeClazz = Class.forName(resultType);
            return resultTypeClazz;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    private void parseEnvironment(Element environments) {
        String aDefault = environments.attributeValue("default");
        List<Element> elementList = environments.elements("environment");
        for (Element element : elementList) {
            String id = element.attributeValue("id");
            if (aDefault.equals(id)) {
                parseDataSource(element.element("dataSource"));
            }
        }
    }

    private void parseDataSource(Element dataSource) {
        String type = dataSource.attributeValue("type");
        // DBCP
        if (type.equals("DBCP")) {
            BasicDataSource basicDataSource = new BasicDataSource();
            Properties properties = parseProperties(dataSource);
            basicDataSource.setDriverClassName(properties.getProperty("db.driver"));
            basicDataSource.setUrl(properties.getProperty("db.url"));
            basicDataSource.setUsername(properties.getProperty("db.username"));
            basicDataSource.setPassword(properties.getProperty("db.password"));
            configuration.setDataSource(basicDataSource);
        }
        // 其他
    }

    private Properties parseProperties(Element element) {
        Properties properties = new Properties();
        List<Element> elementList = element.elements("property");
        for (Element property : elementList) {
            String name = property.attributeValue("name");
            String value = property.attributeValue("value");
            properties.setProperty(name, value);
        }
        return properties;
    }

    private Document getDocument(InputStream inputStream) {
        Document document = null;
        try {
            SAXReader reader = new SAXReader();
            document = reader.read(inputStream);
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        return document;
    }

    private InputStream getInputStreamAsResource(String location) {
        return this.getClass().getClassLoader().getResourceAsStream(location);
    }

}
